package cn.klaw.event;

import cn.hutool.core.util.ReflectUtil;
import cn.klaw.config.ApplicationStartErrorCheck;
import cn.klaw.constant.GlobalConst;
import cn.klaw.module.ProjectEvent;
import cn.klaw.module.ProjectEventInstance;
import cn.klaw.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 自定义事件处理器扫描
 */
@Slf4j
public class ProjectEventListenerAnnotationBeanPostProcessor implements BeanPostProcessor{

    private List<String> scanPackages = Arrays.asList(GlobalConst.PROJECT_SCAN_PACKAGES.split(";"));
    private List<Class> initedClass = new ArrayList<>();


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean)
                : bean.getClass();
        boolean isMatch = scanPackages.stream().anyMatch(s -> targetClass.getName().startsWith(s));
        if(isMatch) {
            if(!initedClass.contains(targetClass)) {
                ApplicationStartErrorCheck.getApplicationInitThreadPool().execute(() -> {
                    try {
                        initedClass.add(targetClass);
                        Method[] uniqueDeclaredMethods = ReflectionUtils
                                .getUniqueDeclaredMethods(targetClass, ReflectionUtils.USER_DECLARED_METHODS);
                        for (Method method : uniqueDeclaredMethods) {
                            ProjectEventListener listener = AnnotatedElementUtils
                                    .findMergedAnnotation(method, ProjectEventListener.class);
                            if (listener != null) {
                                String[] channel = listener.channel();
                                String[] channelPattern = listener.channelRegex();
                                String[] channelSpel = listener.channelSpel();
                                Set<String> channelSet = Arrays.stream(channel).filter(StringUtil::isNotEmpty).collect(Collectors.toSet());
                                Set<String> channelPatternSet = Arrays.stream(channelPattern).filter(StringUtil::isNotEmpty).collect(Collectors.toSet());
                                Set<String> channelSpelSet = Arrays.stream(channelSpel).filter(StringUtil::isNotEmpty).collect(Collectors.toSet());
                                if (channelSet.isEmpty() && channelPatternSet.isEmpty() && channelSpelSet.isEmpty()) {
                                    throw new IllegalArgumentException(getLogText(beanName, method.getName(), "channel和channelPattern和channelSpel必须填写一个"));
                                }

                                // 检查参数数量小于等于1
                                int paramCount = method.getParameterCount();
                                if (paramCount > 1) {
                                    throw new IllegalArgumentException(getLogText(beanName, method.getName(), "参数只能小于1个"));
                                } else if (paramCount == 1) {
                                    if (!method.getParameterTypes()[0].equals(ProjectEventInstance.class)) {
                                        throw new IllegalArgumentException(getLogText(beanName, method.getName(), "参数只能是import cn.klaw..module.ProjectEventInstance"));
                                    }
                                }

                                String topic = listener.toptic();
                                Assert.hasLength(topic, "主题不允许为空");
                                for (String s : channelSet) {
                                    ProjectEventHolder.addEventListener(topic, new ProjectEventKeyMatcher.Text(s), event -> {
                                        invokeMethod(bean, method, event);
                                    });
                                }
                                for (String s : channelPatternSet) {
                                    ProjectEventHolder.addEventListener(topic, new ProjectEventKeyMatcher.Pattern(s), event -> {
                                        invokeMethod(bean, method, event);
                                    });
                                }
                                for (String s : channelSpelSet) {
                                    ProjectEventHolder.addEventListener(topic, new ProjectEventKeyMatcher.Spel(s), event -> {
                                        invokeMethod(bean, method, event);
                                    });
                                }
                                if(log.isDebugEnabled()) {
                                    log.debug(getLogText(beanName, method.getName(), "注册成功"));
                                }
                            }
                        }
                    } catch (Exception e) {
                        ApplicationStartErrorCheck.setStartError();
                        throw e;
                    }
                });
            }
        }
        return bean;
    }

    private void invokeMethod(Object bean, Method method, ProjectEvent topicEvent) {
        ReflectUtil.invoke(bean, method, topicEvent);
    }

    private String getLogText(String bean, String method, String text) {
        return "@ProjectEventListener注解的对象[" + bean + "]方法[" + method + "]" + text;
    }
}
